gitxelectron 1.2.9 → 1.3.1

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
@@ -577,7 +577,12 @@ function initGit(sessionId, baseDir) {
577
577
  baseDir,
578
578
  binary: "git",
579
579
  maxConcurrentProcesses: 6,
580
- unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true },
580
+ unsafe: {
581
+ allowUnsafeEditor: true,
582
+ allowUnsafeAskPass: true,
583
+ allowUnsafeCredentialHelper: true,
584
+ allowUnsafeFilter: true
585
+ },
581
586
  progress: ({ method, stage, progress }) => {
582
587
  try {
583
588
  const wc = electron.webContents.fromId(sessionId);
@@ -598,7 +603,12 @@ function createSafeGit(baseDir, sessionId) {
598
603
  const options = {
599
604
  binary: "git",
600
605
  maxConcurrentProcesses: 6,
601
- unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true },
606
+ unsafe: {
607
+ allowUnsafeEditor: true,
608
+ allowUnsafeAskPass: true,
609
+ allowUnsafeCredentialHelper: true,
610
+ allowUnsafeFilter: true
611
+ },
602
612
  progress: ({ method, stage, progress }) => {
603
613
  if (!sessionId) return;
604
614
  try {
@@ -1017,15 +1027,148 @@ async function pushToRemote(sessionId, remote, branch) {
1017
1027
  }
1018
1028
  }
1019
1029
  async function pullFromRemote(sessionId, remote, branch) {
1020
- const { git } = getGitSession(sessionId);
1030
+ const { git, repoPath } = getGitSession(sessionId);
1021
1031
  const remotes = await getRemotes(sessionId);
1022
- const remoteObj = remotes.find((r) => r.name === remote);
1023
- const options = { "--allow-unrelated-histories": null };
1024
- if (remoteObj && remoteObj.refs.fetch) {
1025
- const authUrl = injectAuthToken(sessionId, remoteObj.refs.fetch);
1026
- return await git.pull(authUrl, branch, options);
1032
+ if (!remotes || remotes.length === 0) {
1033
+ throw new Error("No remote repository configured to pull from.");
1034
+ }
1035
+ const remoteObj = (remote ? remotes.find((r) => r.name === remote) : null) || remotes[0];
1036
+ const targetRemote = remoteObj.name;
1037
+ let targetBranch = branch;
1038
+ if (!targetBranch) {
1039
+ const status = await git.status();
1040
+ targetBranch = status.current || "main";
1041
+ }
1042
+ const authUrl = remoteObj.refs?.fetch ? injectAuthToken(sessionId, remoteObj.refs.fetch) : targetRemote;
1043
+ let didStash = false;
1044
+ try {
1045
+ const status = await git.status();
1046
+ if (!status.isClean()) {
1047
+ await git.raw(["stash", "push", "-u", "-m", "gitquickpush-autostash-before-pull"]);
1048
+ didStash = true;
1049
+ }
1050
+ } catch (err) {
1051
+ console.warn("Stash check before pull warning:", err);
1052
+ }
1053
+ try {
1054
+ const pullArgs = [
1055
+ "-c",
1056
+ "credential.helper=",
1057
+ "-c",
1058
+ "core.longpaths=true",
1059
+ "-c",
1060
+ "filter.lfs.required=false",
1061
+ "-c",
1062
+ "filter.lfs.smudge=",
1063
+ "-c",
1064
+ "http.postBuffer=524288000",
1065
+ "pull",
1066
+ "--no-rebase",
1067
+ "--allow-unrelated-histories",
1068
+ authUrl,
1069
+ targetBranch
1070
+ ];
1071
+ const pullOutput = await new Promise((resolve, reject) => {
1072
+ const child = child_process.spawn("git", pullArgs, {
1073
+ cwd: repoPath,
1074
+ env: {
1075
+ ...process.env,
1076
+ GIT_LFS_SKIP_SMUDGE: "1",
1077
+ GCM_INTERACTIVE: "false",
1078
+ GIT_TERMINAL_PROMPT: "0",
1079
+ GIT_ASKPASS: "echo"
1080
+ }
1081
+ });
1082
+ let out = "";
1083
+ child.stdout.on("data", (d) => {
1084
+ out += d.toString();
1085
+ });
1086
+ child.stderr.on("data", (d) => {
1087
+ out += d.toString();
1088
+ });
1089
+ child.on("close", (code) => {
1090
+ if (code === 0) {
1091
+ resolve(out);
1092
+ } else {
1093
+ reject(new Error(out || `Git pull exited with code ${code}`));
1094
+ }
1095
+ });
1096
+ child.on("error", (err) => reject(err));
1097
+ });
1098
+ if (didStash) {
1099
+ try {
1100
+ await git.raw(["stash", "pop"]);
1101
+ } catch (popErr) {
1102
+ console.warn("Auto-stash pop had merge notice:", popErr?.message);
1103
+ }
1104
+ }
1105
+ const alreadyUpToDate = pullOutput.includes("Already up to date.") || pullOutput.includes("Already up-to-date.");
1106
+ const lines = pullOutput.split(/[\r\n]+/).map((l) => l.trim());
1107
+ const changedFiles2 = [];
1108
+ for (const l of lines) {
1109
+ if (l.includes("|") && (l.includes("+") || l.includes("-"))) {
1110
+ const filePart = l.split("|")[0].trim();
1111
+ if (filePart) changedFiles2.push(filePart);
1112
+ }
1113
+ }
1114
+ const message = alreadyUpToDate ? "Already up to date with remote." : changedFiles2.length > 0 ? `Successfully pulled ${changedFiles2.length} file(s) from team!` : "Successfully pulled latest changes from team!";
1115
+ return {
1116
+ success: true,
1117
+ alreadyUpToDate,
1118
+ message,
1119
+ filesChanged: changedFiles2,
1120
+ raw: pullOutput
1121
+ };
1122
+ } catch (err) {
1123
+ if (didStash) {
1124
+ try {
1125
+ await git.raw(["stash", "pop"]);
1126
+ } catch {
1127
+ }
1128
+ }
1129
+ const token = getToken(sessionId);
1130
+ let cleanMsg = err?.message || String(err);
1131
+ if (token) {
1132
+ cleanMsg = cleanMsg.split(token).join("***");
1133
+ }
1134
+ cleanMsg = cleanMsg.replace(/https:\/\/[^@]+@github\.com/g, "https://github.com");
1135
+ throw new Error(cleanMsg);
1136
+ }
1137
+ }
1138
+ async function checkRemoteUpdates(sessionId) {
1139
+ const { git, repoPath } = getGitSession(sessionId);
1140
+ try {
1141
+ const remotes = await getRemotes(sessionId);
1142
+ if (!remotes || remotes.length === 0) return { behind: 0, ahead: 0, current: "" };
1143
+ const remoteObj = remotes[0];
1144
+ const authUrl = remoteObj.refs?.fetch ? injectAuthToken(sessionId, remoteObj.refs.fetch) : remoteObj.name;
1145
+ await new Promise((resolve) => {
1146
+ const child = child_process.spawn(
1147
+ "git",
1148
+ ["-c", "credential.helper=", "-c", "core.longpaths=true", "fetch", "--prune", authUrl],
1149
+ {
1150
+ cwd: repoPath,
1151
+ env: {
1152
+ ...process.env,
1153
+ GIT_LFS_SKIP_SMUDGE: "1",
1154
+ GCM_INTERACTIVE: "false",
1155
+ GIT_TERMINAL_PROMPT: "0",
1156
+ GIT_ASKPASS: "echo"
1157
+ }
1158
+ }
1159
+ );
1160
+ child.on("close", () => resolve());
1161
+ child.on("error", () => resolve());
1162
+ });
1163
+ const status = await git.status();
1164
+ return {
1165
+ behind: status.behind,
1166
+ ahead: status.ahead,
1167
+ current: status.current || "main"
1168
+ };
1169
+ } catch {
1170
+ return { behind: 0, ahead: 0, current: "" };
1027
1171
  }
1028
- return await git.pull(remote, branch, options);
1029
1172
  }
1030
1173
  async function resetToRemote(sessionId, remote, branch) {
1031
1174
  const { git } = getGitSession(sessionId);
@@ -1405,39 +1548,122 @@ async function cloneRepository(sessionId, remoteUrl, destPath) {
1405
1548
  await existingGit.raw(["remote", "add", "origin", authUrl]);
1406
1549
  }
1407
1550
  await existingGit.raw(["-c", "credential.helper=", "fetch", "--progress", "origin"]);
1408
- const st = await existingGit.status();
1409
- const branch = st.current || "main";
1410
- try {
1411
- await existingGit.raw(["-c", "credential.helper=", "pull", "--progress", "origin", branch]);
1412
- } catch (e) {
1413
- }
1551
+ await new Promise((resolve) => {
1552
+ const child = child_process.spawn(
1553
+ "git",
1554
+ ["-c", "core.longpaths=true", "-c", "filter.lfs.required=false", "-c", "filter.lfs.smudge=", "checkout", "-f"],
1555
+ {
1556
+ cwd: destPath,
1557
+ env: { ...process.env, GIT_LFS_SKIP_SMUDGE: "1" }
1558
+ }
1559
+ );
1560
+ child.on("close", () => resolve());
1561
+ child.on("error", () => resolve());
1562
+ });
1414
1563
  return destPath;
1415
1564
  } catch (err) {
1416
1565
  console.warn("Re-using existing repo failed, falling through to clone attempt:", err);
1417
1566
  }
1418
1567
  }
1419
- if (destExists && destIsEmpty) {
1420
- const emptyDirGit = createSafeGit(destPath, sessionId);
1421
- await emptyDirGit.raw(["-c", "credential.helper=", "clone", "--progress", authUrl, "."]);
1422
- try {
1423
- await createSafeGit(void 0, sessionId).raw(["config", "--global", "--add", "safe.directory", destPath]);
1424
- } catch (e) {
1425
- }
1426
- return destPath;
1427
- }
1428
1568
  if (destExists && !destIsEmpty && !destHasGit) {
1429
1569
  throw new Error(
1430
1570
  `The folder '${path__namespace.basename(destPath)}' already contains files. Please select an empty folder or a different location to clone.`
1431
1571
  );
1432
1572
  }
1433
- const baseDir = path__namespace.dirname(destPath);
1434
- const targetDirName = path__namespace.basename(destPath);
1435
- const parentGit = createSafeGit(baseDir, sessionId);
1436
- await parentGit.raw(["-c", "credential.helper=", "clone", "--progress", authUrl, targetDirName]);
1437
- try {
1438
- await createSafeGit(void 0, sessionId).raw(["config", "--global", "--add", "safe.directory", destPath]);
1439
- } catch (e) {
1573
+ const wc = electron.webContents.fromId(sessionId);
1574
+ const cloneArgs = [
1575
+ "-c",
1576
+ "credential.helper=",
1577
+ "-c",
1578
+ "core.longpaths=true",
1579
+ "-c",
1580
+ "filter.lfs.required=false",
1581
+ "-c",
1582
+ "filter.lfs.smudge=",
1583
+ "-c",
1584
+ "http.postBuffer=524288000",
1585
+ "-c",
1586
+ "core.compression=0",
1587
+ "-c",
1588
+ "pack.windowMemory=256m",
1589
+ "clone",
1590
+ "--progress",
1591
+ authUrl
1592
+ ];
1593
+ let cwd;
1594
+ if (destExists && destIsEmpty) {
1595
+ cloneArgs.push(".");
1596
+ cwd = destPath;
1597
+ } else {
1598
+ cloneArgs.push(path__namespace.basename(destPath));
1599
+ cwd = path__namespace.dirname(destPath);
1440
1600
  }
1601
+ await new Promise((resolve, reject) => {
1602
+ const child = child_process.spawn("git", cloneArgs, {
1603
+ cwd,
1604
+ env: {
1605
+ ...process.env,
1606
+ GIT_LFS_SKIP_SMUDGE: "1",
1607
+ GCM_INTERACTIVE: "false",
1608
+ GIT_TERMINAL_PROMPT: "0",
1609
+ GIT_ASKPASS: "echo"
1610
+ }
1611
+ });
1612
+ const errorLines = [];
1613
+ child.stderr.on("data", (data) => {
1614
+ const text = data.toString();
1615
+ const match = text.match(
1616
+ /(Receiving objects|Resolving deltas|Counting objects|Compressing objects):\s+(\d+)%/i
1617
+ );
1618
+ if (match && wc) {
1619
+ const stage = match[1];
1620
+ const progress = parseInt(match[2], 10);
1621
+ try {
1622
+ wc.send("git-progress", { method: "clone", stage, progress });
1623
+ } catch {
1624
+ }
1625
+ }
1626
+ const lines = text.split(/[\r\n]+/).map((l) => l.trim()).filter(Boolean);
1627
+ for (const line of lines) {
1628
+ if (!line.startsWith("Receiving objects:") && !line.startsWith("Resolving deltas:") && !line.startsWith("Counting objects:") && !line.startsWith("Compressing objects:") && !line.startsWith("Cloning into") && !line.startsWith("remote:")) {
1629
+ errorLines.push(line);
1630
+ if (errorLines.length > 10) errorLines.shift();
1631
+ }
1632
+ }
1633
+ });
1634
+ child.on("error", (err) => reject(err));
1635
+ child.on("close", async (code) => {
1636
+ if (code === 0) {
1637
+ try {
1638
+ await createSafeGit(void 0, sessionId).raw([
1639
+ "config",
1640
+ "--global",
1641
+ "--add",
1642
+ "safe.directory",
1643
+ destPath
1644
+ ]);
1645
+ } catch {
1646
+ }
1647
+ resolve();
1648
+ } else {
1649
+ const token = getToken(sessionId);
1650
+ let errorMsg = errorLines.join("\n") || `Git clone failed (exit code ${code})`;
1651
+ if (token) {
1652
+ errorMsg = errorMsg.split(token).join("***");
1653
+ }
1654
+ errorMsg = errorMsg.replace(/https:\/\/[^@]+@github\.com/g, "https://github.com");
1655
+ if (errorMsg.includes("terminal prompts disabled") || errorMsg.includes("Authentication failed") || errorMsg.includes("could not read Username")) {
1656
+ reject(
1657
+ new Error(
1658
+ "Authentication failed for GitHub repository. Please verify your account is logged in and has access to this repository."
1659
+ )
1660
+ );
1661
+ } else {
1662
+ reject(new Error(errorMsg));
1663
+ }
1664
+ }
1665
+ });
1666
+ });
1441
1667
  return destPath;
1442
1668
  } catch (err) {
1443
1669
  const token = getToken(sessionId);
@@ -1802,6 +2028,7 @@ const getLastWorkspace = async () => {
1802
2028
  };
1803
2029
  let currentWatcher = null;
1804
2030
  let syncTimeout = null;
2031
+ let remotePollInterval = null;
1805
2032
  let isAutoSyncEnabled = false;
1806
2033
  let isResolving = false;
1807
2034
  let globalOnLog = null;
@@ -1822,13 +2049,33 @@ function toggleAutoSync(sessionId, repoPath, enabled, onLog, onErrorApproval, on
1822
2049
  }
1823
2050
  if (syncTimeout) {
1824
2051
  clearTimeout(syncTimeout);
2052
+ syncTimeout = null;
2053
+ }
2054
+ if (remotePollInterval) {
2055
+ clearInterval(remotePollInterval);
2056
+ remotePollInterval = null;
1825
2057
  }
1826
2058
  changedFiles.clear();
1827
2059
  if (!enabled) {
1828
2060
  onLog("Auto-Sync disabled.");
1829
2061
  return;
1830
2062
  }
1831
- onLog("⚡ Auto-Sync enabled. Watching for changes...");
2063
+ onLog("⚡ Auto-Sync enabled (Watching local edits & syncing team changes from GitHub)...");
2064
+ remotePollInterval = setInterval(async () => {
2065
+ if (!isAutoSyncEnabled || isResolving) return;
2066
+ try {
2067
+ const updates = await checkRemoteUpdates(sessionId);
2068
+ if (updates.behind > 0 && !isResolving) {
2069
+ onLog(`📥 Found ${updates.behind} new commit(s) from team members on GitHub! Syncing to your computer...`);
2070
+ const result = await pullFromRemote(sessionId);
2071
+ onLog(`✅ ${result.message}`);
2072
+ if (globalOnResolveComplete) {
2073
+ globalOnResolveComplete();
2074
+ }
2075
+ }
2076
+ } catch {
2077
+ }
2078
+ }, 25e3);
1832
2079
  currentWatcher = chokidar.watch(repoPath, {
1833
2080
  ignored: [
1834
2081
  /(^|[\/\\])\../,
@@ -2195,6 +2442,9 @@ electron.app.whenReady().then(async () => {
2195
2442
  electron.ipcMain.handle("pull-from-remote", async (event, remote, branch) => {
2196
2443
  return await pullFromRemote(event.sender.id, remote, branch);
2197
2444
  });
2445
+ electron.ipcMain.handle("check-remote-updates", async (event) => {
2446
+ return await checkRemoteUpdates(event.sender.id);
2447
+ });
2198
2448
  electron.ipcMain.handle("reset-to-remote", async (event, remote, branch) => {
2199
2449
  return await resetToRemote(event.sender.id, remote, branch);
2200
2450
  });
@@ -20,6 +20,7 @@ const api = {
20
20
  commitChanges: (message) => electron.ipcRenderer.invoke("commit-changes", message),
21
21
  pushToRemote: (remote, branch) => electron.ipcRenderer.invoke("push-to-remote", remote, branch),
22
22
  pullFromRemote: (remote, branch) => electron.ipcRenderer.invoke("pull-from-remote", remote, branch),
23
+ checkRemoteUpdates: () => electron.ipcRenderer.invoke("check-remote-updates"),
23
24
  resetToRemote: (remote, branch) => electron.ipcRenderer.invoke("reset-to-remote", remote, branch),
24
25
  fetchRemote: () => electron.ipcRenderer.invoke("fetch-remote"),
25
26
  getBranches: () => electron.ipcRenderer.invoke("get-branches"),