gitxelectron 1.3.0 → 1.3.2

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, `+refs/heads/*:refs/remotes/${remoteObj.name}/*`],
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);
@@ -1885,6 +2028,7 @@ const getLastWorkspace = async () => {
1885
2028
  };
1886
2029
  let currentWatcher = null;
1887
2030
  let syncTimeout = null;
2031
+ let remotePollInterval = null;
1888
2032
  let isAutoSyncEnabled = false;
1889
2033
  let isResolving = false;
1890
2034
  let globalOnLog = null;
@@ -1905,17 +2049,39 @@ function toggleAutoSync(sessionId, repoPath, enabled, onLog, onErrorApproval, on
1905
2049
  }
1906
2050
  if (syncTimeout) {
1907
2051
  clearTimeout(syncTimeout);
2052
+ syncTimeout = null;
2053
+ }
2054
+ if (remotePollInterval) {
2055
+ clearInterval(remotePollInterval);
2056
+ remotePollInterval = null;
1908
2057
  }
1909
2058
  changedFiles.clear();
1910
2059
  if (!enabled) {
1911
2060
  onLog("Auto-Sync disabled.");
1912
2061
  return;
1913
2062
  }
1914
- 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);
1915
2079
  currentWatcher = chokidar.watch(repoPath, {
1916
2080
  ignored: [
1917
- /(^|[\/\\])\../,
1918
- // ignore dotfiles (.git, .DS_Store)
2081
+ /(^|[\/\\])\.git([\/\\]|$)/,
2082
+ // strictly ignore .git directory
2083
+ /(^|[\/\\])\.DS_Store$/,
2084
+ // strictly ignore .DS_Store files
1919
2085
  /node_modules/,
1920
2086
  /dist/,
1921
2087
  /build/,
@@ -2278,6 +2444,9 @@ electron.app.whenReady().then(async () => {
2278
2444
  electron.ipcMain.handle("pull-from-remote", async (event, remote, branch) => {
2279
2445
  return await pullFromRemote(event.sender.id, remote, branch);
2280
2446
  });
2447
+ electron.ipcMain.handle("check-remote-updates", async (event) => {
2448
+ return await checkRemoteUpdates(event.sender.id);
2449
+ });
2281
2450
  electron.ipcMain.handle("reset-to-remote", async (event, remote, branch) => {
2282
2451
  return await resetToRemote(event.sender.id, remote, branch);
2283
2452
  });
@@ -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"),