gitxelectron 1.2.7 → 1.2.9

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
@@ -5,7 +5,6 @@ const fs = require("fs/promises");
5
5
  const simpleGit = require("simple-git");
6
6
  const crypto = require("crypto");
7
7
  const child_process = require("child_process");
8
- const readline = require("readline");
9
8
  const generativeAi = require("@google/generative-ai");
10
9
  const OpenAI = require("openai");
11
10
  const chokidar = require("chokidar");
@@ -28,7 +27,6 @@ function _interopNamespaceDefault(e) {
28
27
  }
29
28
  const path__namespace = /* @__PURE__ */ _interopNamespaceDefault(path);
30
29
  const fs__namespace = /* @__PURE__ */ _interopNamespaceDefault(fs);
31
- const readline__namespace = /* @__PURE__ */ _interopNamespaceDefault(readline);
32
30
  const GITHUB_CLIENT_ID = "Ov23livWfiaPADGiwSXq";
33
31
  let accounts = [];
34
32
  const activeSessions = /* @__PURE__ */ new Map();
@@ -867,11 +865,8 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
867
865
  if (remoteObj && remoteObj.refs.push) {
868
866
  authUrl = injectAuthToken(sessionId, remoteObj.refs.push);
869
867
  }
870
- const child = child_process.spawn("git", ["status", "--porcelain"], { cwd: repoPath });
871
- const rl = readline__namespace.createInterface({
872
- input: child.stdout,
873
- crlfDelay: Infinity
874
- });
868
+ const child = child_process.spawn("git", ["status", "--porcelain", "-z"], { cwd: repoPath });
869
+ let buffer = "";
875
870
  let currentChunk = [];
876
871
  let chunkCount = 0;
877
872
  let processedFiles = 0;
@@ -902,10 +897,34 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
902
897
  eta: etaStr
903
898
  });
904
899
  }
900
+ const LARGE_FILE_LIMIT = 50 * 1024 * 1024;
901
+ const largeFiles = [];
902
+ await Promise.all(chunk.map(async (file) => {
903
+ try {
904
+ const stats = await fs__namespace.stat(path__namespace.join(repoPath, file));
905
+ if (stats.isFile() && stats.size > LARGE_FILE_LIMIT) {
906
+ largeFiles.push(file);
907
+ }
908
+ } catch (e) {
909
+ }
910
+ }));
911
+ if (largeFiles.length > 0) {
912
+ try {
913
+ await git.raw(["lfs", "install"]);
914
+ for (const file of largeFiles) {
915
+ await git.raw(["lfs", "track", file]);
916
+ }
917
+ if (!chunk.includes(".gitattributes")) {
918
+ chunk.push(".gitattributes");
919
+ }
920
+ } catch (e) {
921
+ console.warn("Git LFS failed:", e);
922
+ }
923
+ }
905
924
  const tempFile = path__namespace.join(repoPath, ".git", "massive_chunk.txt");
906
- await fs__namespace.writeFile(tempFile, chunk.join("\n"), "utf-8");
925
+ await fs__namespace.writeFile(tempFile, chunk.join("\0") + "\0", "utf-8");
907
926
  try {
908
- await git.raw(["-c", "core.safecrlf=false", "add", "--force", "--pathspec-from-file=" + tempFile]);
927
+ await git.raw(["-c", "core.safecrlf=false", "add", "--force", "--pathspec-from-file=" + tempFile, "--pathspec-file-nul"]);
909
928
  } catch (e) {
910
929
  if (e.message && e.message.includes("fatal:")) {
911
930
  throw e;
@@ -916,41 +935,49 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
916
935
  } catch (e) {
917
936
  }
918
937
  }
919
- try {
920
- await git.commit(`Auto-sync chunk ${chunkCount} (Massive Push)`);
921
- } catch (e) {
922
- }
923
- try {
924
- await git.raw(["-c", "credential.helper=", "push", authUrl, branch]);
925
- } catch (err) {
926
- if (err.message && (err.message.includes("non-fast-forward") || err.message.includes("fetch first") || err.message.includes("branch is behind"))) {
927
- await git.pull(authUrl, branch, { "--rebase": null, "--autostash": null });
928
- await git.raw(["-c", "credential.helper=", "push", authUrl, branch]);
929
- } else {
930
- if (err.message && !err.message.includes("nothing to commit")) {
931
- throw err;
932
- }
933
- }
934
- }
935
938
  };
936
- for await (const line of rl) {
937
- if (line.length < 3) continue;
938
- let file = line.substring(3).trim();
939
- if (file.startsWith('"') && file.endsWith('"')) {
940
- file = file.substring(1, file.length - 1);
941
- }
942
- if (!file) continue;
943
- currentChunk.push(file);
944
- if (currentChunk.length >= chunkSize) {
945
- rl.pause();
946
- await processChunk(currentChunk);
947
- currentChunk = [];
948
- rl.resume();
939
+ for await (const chunkData of child.stdout) {
940
+ buffer += chunkData.toString("utf-8");
941
+ let nullIdx;
942
+ while ((nullIdx = buffer.indexOf("\0")) !== -1) {
943
+ const entry = buffer.slice(0, nullIdx);
944
+ buffer = buffer.slice(nullIdx + 1);
945
+ if (entry.length < 3) continue;
946
+ const file = entry.substring(3);
947
+ if (!file) continue;
948
+ currentChunk.push(file);
949
+ if (currentChunk.length >= chunkSize) {
950
+ child.stdout.pause();
951
+ await processChunk(currentChunk);
952
+ currentChunk = [];
953
+ child.stdout.resume();
954
+ }
949
955
  }
950
956
  }
951
957
  if (currentChunk.length > 0) {
952
958
  await processChunk(currentChunk);
953
959
  }
960
+ if (onProgress) onProgress("Committing all files...");
961
+ try {
962
+ await git.commit(`Auto-sync (Massive Push)`);
963
+ } catch (e) {
964
+ }
965
+ if (onProgress) onProgress("Pushing to remote...");
966
+ 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"];
967
+ try {
968
+ await git.raw([...pushArgs, authUrl, branch]);
969
+ } catch (err) {
970
+ if (err.message && (err.message.includes("non-fast-forward") || err.message.includes("fetch first") || err.message.includes("branch is behind"))) {
971
+ if (onProgress) onProgress("Remote branch is ahead. Pulling changes...");
972
+ await git.pull(authUrl, branch, { "--rebase": null, "--autostash": null });
973
+ if (onProgress) onProgress("Pushing to remote after pull...");
974
+ await git.raw([...pushArgs, authUrl, branch]);
975
+ } else {
976
+ if (err.message && !err.message.includes("nothing to commit")) {
977
+ throw err;
978
+ }
979
+ }
980
+ }
954
981
  if (onProgress) onProgress("✅ Massive push completed successfully!");
955
982
  return true;
956
983
  }
@@ -964,11 +991,12 @@ async function pushToRemote(sessionId, remote, branch) {
964
991
  GIT_ASKPASS: "echo"
965
992
  });
966
993
  const doPush = async () => {
994
+ 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"];
967
995
  if (remoteObj && remoteObj.refs.push) {
968
996
  const authUrl = injectAuthToken(sessionId, remoteObj.refs.push);
969
- return await tempGit.raw(["-c", "credential.helper=", "push", authUrl, branch]);
997
+ return await tempGit.raw([...pushArgs, authUrl, branch]);
970
998
  }
971
- return await tempGit.raw(["-c", "credential.helper=", "push", remote, branch]);
999
+ return await tempGit.raw([...pushArgs, remote, branch]);
972
1000
  };
973
1001
  try {
974
1002
  return await doPush();
@@ -1376,11 +1404,11 @@ async function cloneRepository(sessionId, remoteUrl, destPath) {
1376
1404
  } else {
1377
1405
  await existingGit.raw(["remote", "add", "origin", authUrl]);
1378
1406
  }
1379
- await existingGit.raw(["-c", "credential.helper=", "fetch", "origin"]);
1407
+ await existingGit.raw(["-c", "credential.helper=", "fetch", "--progress", "origin"]);
1380
1408
  const st = await existingGit.status();
1381
1409
  const branch = st.current || "main";
1382
1410
  try {
1383
- await existingGit.raw(["-c", "credential.helper=", "pull", "origin", branch]);
1411
+ await existingGit.raw(["-c", "credential.helper=", "pull", "--progress", "origin", branch]);
1384
1412
  } catch (e) {
1385
1413
  }
1386
1414
  return destPath;
@@ -1390,7 +1418,7 @@ async function cloneRepository(sessionId, remoteUrl, destPath) {
1390
1418
  }
1391
1419
  if (destExists && destIsEmpty) {
1392
1420
  const emptyDirGit = createSafeGit(destPath, sessionId);
1393
- await emptyDirGit.raw(["-c", "credential.helper=", "clone", authUrl, "."]);
1421
+ await emptyDirGit.raw(["-c", "credential.helper=", "clone", "--progress", authUrl, "."]);
1394
1422
  try {
1395
1423
  await createSafeGit(void 0, sessionId).raw(["config", "--global", "--add", "safe.directory", destPath]);
1396
1424
  } catch (e) {
@@ -1405,7 +1433,7 @@ async function cloneRepository(sessionId, remoteUrl, destPath) {
1405
1433
  const baseDir = path__namespace.dirname(destPath);
1406
1434
  const targetDirName = path__namespace.basename(destPath);
1407
1435
  const parentGit = createSafeGit(baseDir, sessionId);
1408
- await parentGit.raw(["-c", "credential.helper=", "clone", authUrl, targetDirName]);
1436
+ await parentGit.raw(["-c", "credential.helper=", "clone", "--progress", authUrl, targetDirName]);
1409
1437
  try {
1410
1438
  await createSafeGit(void 0, sessionId).raw(["config", "--global", "--add", "safe.directory", destPath]);
1411
1439
  } catch (e) {
@@ -1426,6 +1454,85 @@ async function cloneRepository(sessionId, remoteUrl, destPath) {
1426
1454
  throw new Error(cleanMsg);
1427
1455
  }
1428
1456
  }
1457
+ async function removeGitMetadata(_sessionId, repoPath) {
1458
+ try {
1459
+ const gitDir = path__namespace.join(repoPath, ".git");
1460
+ await fs__namespace.rm(gitDir, { recursive: true, force: true });
1461
+ return true;
1462
+ } catch (e) {
1463
+ console.error("Failed to remove git metadata:", e);
1464
+ return false;
1465
+ }
1466
+ }
1467
+ async function getDirectoryGitInfo(dir) {
1468
+ try {
1469
+ const isRepo = await isGitRepo(dir);
1470
+ if (!isRepo) return { isRepo: false, remotes: [] };
1471
+ const tempGit = createSafeGit(dir);
1472
+ let rootPath = void 0;
1473
+ try {
1474
+ rootPath = (await tempGit.revparse(["--show-toplevel"])).trim();
1475
+ if (rootPath) rootPath = path__namespace.normalize(rootPath);
1476
+ } catch {
1477
+ }
1478
+ const remotes = await tempGit.getRemotes(true);
1479
+ const origin = remotes.find((r) => r.name === "origin") || remotes[0];
1480
+ let repoName = void 0;
1481
+ let remoteUrl = void 0;
1482
+ if (origin && origin.refs && (origin.refs.fetch || origin.refs.push)) {
1483
+ remoteUrl = origin.refs.fetch || origin.refs.push;
1484
+ const match = remoteUrl.match(/\/([^/]+?)(?:\.git)?$/);
1485
+ if (match) {
1486
+ repoName = match[1];
1487
+ }
1488
+ }
1489
+ return {
1490
+ isRepo: true,
1491
+ rootPath: rootPath || dir,
1492
+ remotes: JSON.parse(JSON.stringify(remotes)),
1493
+ repoName,
1494
+ remoteUrl
1495
+ };
1496
+ } catch {
1497
+ return { isRepo: false, remotes: [] };
1498
+ }
1499
+ }
1500
+ async function scanForRepoFolder(repoName, candidateBaseDirs) {
1501
+ const cleanName = repoName.toLowerCase().trim();
1502
+ const visited = /* @__PURE__ */ new Set();
1503
+ for (const base of candidateBaseDirs) {
1504
+ if (!base || visited.has(base.toLowerCase())) continue;
1505
+ visited.add(base.toLowerCase());
1506
+ try {
1507
+ const directCandidate = path__namespace.join(base, repoName);
1508
+ try {
1509
+ const stat = await fs__namespace.stat(directCandidate);
1510
+ if (stat.isDirectory()) {
1511
+ const info = await getDirectoryGitInfo(directCandidate);
1512
+ if (info.isRepo) {
1513
+ if (!info.repoName || info.repoName.toLowerCase() === cleanName) {
1514
+ return directCandidate;
1515
+ }
1516
+ }
1517
+ }
1518
+ } catch {
1519
+ }
1520
+ const entries = await fs__namespace.readdir(base, { withFileTypes: true }).catch(() => []);
1521
+ for (const entry of entries) {
1522
+ if (!entry.isDirectory()) continue;
1523
+ const childPath = path__namespace.join(base, entry.name);
1524
+ if (entry.name.toLowerCase() === cleanName) {
1525
+ const info = await getDirectoryGitInfo(childPath);
1526
+ if (info.isRepo) {
1527
+ return childPath;
1528
+ }
1529
+ }
1530
+ }
1531
+ } catch {
1532
+ }
1533
+ }
1534
+ return null;
1535
+ }
1429
1536
  const SYSTEM_PROMPT = `You are an autonomous Git Healer Agent for the "GitElectron" app.
1430
1537
  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.
1431
1538
  You will receive the Git Status and the Raw Error Message.
@@ -2027,11 +2134,15 @@ electron.app.whenReady().then(async () => {
2027
2134
  electron.ipcMain.handle("get-cli-repo", () => {
2028
2135
  return initialRepo;
2029
2136
  });
2030
- electron.ipcMain.handle("select-directory", async (event) => {
2137
+ electron.ipcMain.handle("select-directory", async (event, defaultPath) => {
2031
2138
  const sessionId = event.sender.id;
2032
- const result = await electron.dialog.showOpenDialog({
2139
+ const options = {
2033
2140
  properties: ["openDirectory"]
2034
- });
2141
+ };
2142
+ if (defaultPath) {
2143
+ options.defaultPath = defaultPath;
2144
+ }
2145
+ const result = await electron.dialog.showOpenDialog(options);
2035
2146
  if (result.canceled || result.filePaths.length === 0) {
2036
2147
  return null;
2037
2148
  }
@@ -2068,6 +2179,17 @@ electron.app.whenReady().then(async () => {
2068
2179
  return await commitChanges(event.sender.id, message);
2069
2180
  });
2070
2181
  electron.ipcMain.handle("push-to-remote", async (event, remote, branch) => {
2182
+ const status = await getStatus(event.sender.id);
2183
+ if (status.isMassive) {
2184
+ return await pushMassiveRepository(
2185
+ event.sender.id,
2186
+ remote,
2187
+ branch,
2188
+ 5e3,
2189
+ void 0,
2190
+ (data) => event.sender.send("massive-push-progress", data)
2191
+ );
2192
+ }
2071
2193
  return await pushToRemote(event.sender.id, remote, branch);
2072
2194
  });
2073
2195
  electron.ipcMain.handle("pull-from-remote", async (event, remote, branch) => {
@@ -2257,6 +2379,9 @@ electron.app.whenReady().then(async () => {
2257
2379
  electron.ipcMain.handle("clone-repository", async (event, remoteUrl, destPath) => {
2258
2380
  return await cloneRepository(event.sender.id, remoteUrl, destPath);
2259
2381
  });
2382
+ electron.ipcMain.handle("remove-git-metadata", async (event, repoPath) => {
2383
+ return await removeGitMetadata(event.sender.id, repoPath);
2384
+ });
2260
2385
  electron.ipcMain.handle("check-path-exists", async (_event, targetPath) => {
2261
2386
  if (!targetPath) return false;
2262
2387
  try {
@@ -2266,6 +2391,17 @@ electron.app.whenReady().then(async () => {
2266
2391
  return false;
2267
2392
  }
2268
2393
  });
2394
+ electron.ipcMain.handle("get-directory-git-info", async (_event, dirPath) => {
2395
+ if (!dirPath) return { isRepo: false, remotes: [] };
2396
+ return await getDirectoryGitInfo(dirPath);
2397
+ });
2398
+ electron.ipcMain.handle(
2399
+ "scan-for-repo-folder",
2400
+ async (_event, repoName, candidateDirs) => {
2401
+ if (!repoName || !candidateDirs || !candidateDirs.length) return null;
2402
+ return await scanForRepoFolder(repoName, candidateDirs);
2403
+ }
2404
+ );
2269
2405
  electron.ipcMain.handle("upload-files-to-repo", async (event, repoPath) => {
2270
2406
  const window = electron.BrowserWindow.fromWebContents(event.sender);
2271
2407
  if (!repoPath) throw new Error("No repository path specified");
@@ -10,9 +10,10 @@ const api = {
10
10
  electron.ipcRenderer.removeAllListeners("git-progress");
11
11
  electron.ipcRenderer.on("git-progress", (_event, data) => callback(data));
12
12
  },
13
- selectDirectory: () => electron.ipcRenderer.invoke("select-directory"),
13
+ selectDirectory: (defaultPath) => electron.ipcRenderer.invoke("select-directory", defaultPath),
14
14
  setRepoPath: (path) => electron.ipcRenderer.invoke("set-repo-path", path),
15
15
  initRepo: (path) => electron.ipcRenderer.invoke("init-repo", path),
16
+ removeGitMetadata: (repoPath) => electron.ipcRenderer.invoke("remove-git-metadata", repoPath),
16
17
  getStatus: () => electron.ipcRenderer.invoke("get-status"),
17
18
  stageFiles: (files) => electron.ipcRenderer.invoke("stage-files", files),
18
19
  unstageFiles: (files) => electron.ipcRenderer.invoke("unstage-files", files),
@@ -77,6 +78,8 @@ const api = {
77
78
  getUniversalDiff: (workspacePath, file) => electron.ipcRenderer.invoke("get-universal-diff", workspacePath, file),
78
79
  cloneRepository: (remoteUrl, destPath) => electron.ipcRenderer.invoke("clone-repository", remoteUrl, destPath),
79
80
  checkPathExists: (targetPath) => electron.ipcRenderer.invoke("check-path-exists", targetPath),
81
+ getDirectoryGitInfo: (dirPath) => electron.ipcRenderer.invoke("get-directory-git-info", dirPath),
82
+ scanForRepoFolder: (repoName, candidateDirs) => electron.ipcRenderer.invoke("scan-for-repo-folder", repoName, candidateDirs),
80
83
  uploadFilesToRepo: (repoPath) => electron.ipcRenderer.invoke("upload-files-to-repo", repoPath),
81
84
  commitUniversalWorkspace: (workspacePath, msg, branch) => electron.ipcRenderer.invoke("commit-universal-workspace", workspacePath, msg, branch),
82
85
  cleanupUniversalWorkspace: (workspacePath) => electron.ipcRenderer.invoke("cleanup-universal-workspace", workspacePath),