gitxelectron 1.4.1 → 1.4.3

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
@@ -2,9 +2,11 @@
2
2
  const electron = require("electron");
3
3
  const path = require("path");
4
4
  const fs = require("fs/promises");
5
+ const https = require("https");
6
+ const child_process = require("child_process");
5
7
  const simpleGit = require("simple-git");
6
8
  const crypto = require("crypto");
7
- const child_process = require("child_process");
9
+ const fs$1 = require("fs");
8
10
  const generativeAi = require("@google/generative-ai");
9
11
  const OpenAI = require("openai");
10
12
  const chokidar = require("chokidar");
@@ -27,6 +29,7 @@ function _interopNamespaceDefault(e) {
27
29
  }
28
30
  const path__namespace = /* @__PURE__ */ _interopNamespaceDefault(path);
29
31
  const fs__namespace = /* @__PURE__ */ _interopNamespaceDefault(fs);
32
+ const https__namespace = /* @__PURE__ */ _interopNamespaceDefault(https);
30
33
  const GITHUB_CLIENT_ID = "Ov23livWfiaPADGiwSXq";
31
34
  let accounts = [];
32
35
  const activeSessions = /* @__PURE__ */ new Map();
@@ -395,6 +398,25 @@ async function createGitHubRepository(sessionId, name, description, isPrivate, a
395
398
  });
396
399
  if (!res.ok) {
397
400
  const errorData = await res.json().catch(() => ({}));
401
+ const activeAccount = getActiveAccount(sessionId);
402
+ if (res.status === 422 && activeAccount?.username) {
403
+ try {
404
+ const existingRes = await fetch(
405
+ `https://api.github.com/repos/${activeAccount.username}/${encodeURIComponent(name)}`,
406
+ {
407
+ headers: {
408
+ Authorization: `Bearer ${token}`,
409
+ Accept: "application/vnd.github.v3+json",
410
+ "User-Agent": "gitelectron-App"
411
+ }
412
+ }
413
+ );
414
+ if (existingRes.ok) {
415
+ return await existingRes.json();
416
+ }
417
+ } catch {
418
+ }
419
+ }
398
420
  throw new Error(errorData.message || "Failed to create repository");
399
421
  }
400
422
  return await res.json();
@@ -602,16 +624,72 @@ async function declineInvitation(sessionId, invitationId) {
602
624
  }
603
625
  }
604
626
  const gitSessions = /* @__PURE__ */ new Map();
627
+ const sessionRepoPaths = /* @__PURE__ */ new Map();
628
+ function recordSessionPath(sessionId, repoPath) {
629
+ if (sessionId && repoPath) {
630
+ sessionRepoPaths.set(sessionId, repoPath);
631
+ }
632
+ }
633
+ let cachedGitBinary = null;
634
+ function getGitBinary() {
635
+ if (cachedGitBinary) return cachedGitBinary;
636
+ try {
637
+ child_process.execSync("git --version", { stdio: "ignore" });
638
+ cachedGitBinary = "git";
639
+ return "git";
640
+ } catch {
641
+ }
642
+ if (process.platform === "win32") {
643
+ const programFiles = process.env.ProgramFiles || "C:\\Program Files";
644
+ const programFilesX86 = process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)";
645
+ const localAppData = process.env.LOCALAPPDATA || "";
646
+ const userProfile = process.env.USERPROFILE || "";
647
+ const candidates = [
648
+ path__namespace.join(programFiles, "Git", "cmd", "git.exe"),
649
+ path__namespace.join(programFiles, "Git", "bin", "git.exe"),
650
+ path__namespace.join(programFiles, "Git", "mingw64", "bin", "git.exe"),
651
+ path__namespace.join(programFilesX86, "Git", "cmd", "git.exe"),
652
+ path__namespace.join(programFilesX86, "Git", "bin", "git.exe"),
653
+ path__namespace.join(localAppData, "Programs", "Git", "cmd", "git.exe"),
654
+ path__namespace.join(localAppData, "Programs", "Git", "bin", "git.exe"),
655
+ path__namespace.join(userProfile, "scoop", "shims", "git.exe")
656
+ ];
657
+ for (const cand of candidates) {
658
+ try {
659
+ if (fs$1.existsSync(cand)) {
660
+ cachedGitBinary = cand;
661
+ return cand;
662
+ }
663
+ } catch {
664
+ }
665
+ }
666
+ } else if (process.platform === "darwin") {
667
+ const candidates = ["/opt/homebrew/bin/git", "/usr/local/bin/git", "/usr/bin/git", "/bin/git"];
668
+ for (const cand of candidates) {
669
+ try {
670
+ if (fs$1.existsSync(cand)) {
671
+ cachedGitBinary = cand;
672
+ return cand;
673
+ }
674
+ } catch {
675
+ }
676
+ }
677
+ }
678
+ cachedGitBinary = "git";
679
+ return "git";
680
+ }
605
681
  function initGit(sessionId, baseDir) {
682
+ recordSessionPath(sessionId, baseDir);
606
683
  const options = {
607
684
  baseDir,
608
- binary: "git",
685
+ binary: getGitBinary(),
609
686
  maxConcurrentProcesses: 6,
610
687
  unsafe: {
611
688
  allowUnsafeEditor: true,
612
689
  allowUnsafeAskPass: true,
613
690
  allowUnsafeCredentialHelper: true,
614
- allowUnsafeFilter: true
691
+ allowUnsafeFilter: true,
692
+ allowUnsafeCustomBinary: true
615
693
  },
616
694
  progress: ({ method, stage, progress }) => {
617
695
  try {
@@ -628,6 +706,8 @@ function initGit(sessionId, baseDir) {
628
706
  GIT_ASKPASS: "echo"
629
707
  });
630
708
  gitSessions.set(sessionId, { git, repoPath: baseDir });
709
+ checkAndHealZombieGitState(baseDir).catch(() => {
710
+ });
631
711
  (async () => {
632
712
  try {
633
713
  const name = await git.getConfig("user.name");
@@ -645,13 +725,14 @@ function initGit(sessionId, baseDir) {
645
725
  }
646
726
  function createSafeGit(baseDir, sessionId) {
647
727
  const options = {
648
- binary: "git",
728
+ binary: getGitBinary(),
649
729
  maxConcurrentProcesses: 6,
650
730
  unsafe: {
651
731
  allowUnsafeEditor: true,
652
732
  allowUnsafeAskPass: true,
653
733
  allowUnsafeCredentialHelper: true,
654
- allowUnsafeFilter: true
734
+ allowUnsafeFilter: true,
735
+ allowUnsafeCustomBinary: true
655
736
  },
656
737
  progress: ({ method, stage, progress }) => {
657
738
  if (!sessionId) return;
@@ -674,9 +755,31 @@ function createSafeGit(baseDir, sessionId) {
674
755
  }
675
756
  function cleanupGitSession(sessionId) {
676
757
  gitSessions.delete(sessionId);
758
+ sessionRepoPaths.delete(sessionId);
677
759
  }
678
760
  function getGitSession(sessionId) {
679
- const session = gitSessions.get(sessionId);
761
+ let session = gitSessions.get(sessionId);
762
+ if (!session) {
763
+ const knownPath = sessionRepoPaths.get(sessionId);
764
+ if (knownPath) {
765
+ try {
766
+ const gitDir = path__namespace.join(knownPath, ".git");
767
+ if (fs$1.existsSync(gitDir)) {
768
+ initGit(sessionId, knownPath);
769
+ session = gitSessions.get(sessionId);
770
+ }
771
+ } catch (e) {
772
+ console.warn("Auto-recovery of git session failed:", e);
773
+ }
774
+ }
775
+ }
776
+ if (!session && gitSessions.size === 1) {
777
+ const singleSession = gitSessions.values().next().value;
778
+ if (singleSession) {
779
+ gitSessions.set(sessionId, singleSession);
780
+ session = singleSession;
781
+ }
782
+ }
680
783
  if (!session) throw new Error("Git not initialized for this window");
681
784
  return session;
682
785
  }
@@ -732,129 +835,220 @@ async function setupDefaultGitAttributes(repoPath) {
732
835
  } catch {
733
836
  }
734
837
  }
735
- async function isGitRepo(dir) {
736
- let hasGitFolder = false;
838
+ async function findGitRoot(dir) {
839
+ if (!dir) return null;
840
+ const cleanDir = path__namespace.normalize(dir.trim());
737
841
  try {
738
- await fs__namespace.access(path__namespace.join(dir, ".git"));
739
- hasGitFolder = true;
842
+ if (fs$1.existsSync(path__namespace.join(cleanDir, ".git"))) {
843
+ try {
844
+ await createSafeGit().raw(["config", "--global", "--add", "safe.directory", cleanDir]);
845
+ } catch {
846
+ }
847
+ return cleanDir;
848
+ }
740
849
  } catch {
741
- hasGitFolder = false;
742
850
  }
743
851
  try {
744
- const tempGit = simpleGit.simpleGit(dir).env({
745
- ...process.env,
746
- GCM_INTERACTIVE: "false",
747
- GIT_TERMINAL_PROMPT: "0",
748
- GIT_ASKPASS: "echo"
749
- });
852
+ const tempGit = createSafeGit(cleanDir);
750
853
  const isRepo = await tempGit.checkIsRepo();
751
- if (isRepo) return true;
752
- if (hasGitFolder) {
753
- await simpleGit.simpleGit().raw(["config", "--global", "--add", "safe.directory", dir]);
754
- return await tempGit.checkIsRepo();
854
+ if (isRepo) {
855
+ const topLevel = (await tempGit.revparse(["--show-toplevel"])).trim();
856
+ if (topLevel) {
857
+ const normTop = path__namespace.normalize(topLevel);
858
+ try {
859
+ await createSafeGit().raw(["config", "--global", "--add", "safe.directory", normTop]);
860
+ } catch {
861
+ }
862
+ return normTop;
863
+ }
864
+ return cleanDir;
865
+ }
866
+ } catch {
867
+ }
868
+ try {
869
+ let current = path__namespace.dirname(cleanDir);
870
+ while (current && current.length > 3) {
871
+ if (fs$1.existsSync(path__namespace.join(current, ".git"))) {
872
+ try {
873
+ await createSafeGit().raw(["config", "--global", "--add", "safe.directory", current]);
874
+ } catch {
875
+ }
876
+ return current;
877
+ }
878
+ const parent = path__namespace.dirname(current);
879
+ if (!parent || parent === current) break;
880
+ current = parent;
881
+ }
882
+ } catch {
883
+ }
884
+ try {
885
+ const entries = await fs__namespace.readdir(cleanDir, { withFileTypes: true });
886
+ for (const entry of entries) {
887
+ if (entry.isDirectory() && !entry.name.startsWith(".")) {
888
+ const subPath = path__namespace.join(cleanDir, entry.name);
889
+ if (fs$1.existsSync(path__namespace.join(subPath, ".git"))) {
890
+ try {
891
+ await createSafeGit().raw(["config", "--global", "--add", "safe.directory", subPath]);
892
+ } catch {
893
+ }
894
+ return subPath;
895
+ }
896
+ }
755
897
  }
756
- return false;
757
898
  } catch {
758
- if (hasGitFolder) {
899
+ }
900
+ return null;
901
+ }
902
+ async function isGitRepo(dir) {
903
+ if (!dir) return false;
904
+ const cleanDir = path__namespace.normalize(dir.trim());
905
+ try {
906
+ if (fs$1.existsSync(path__namespace.join(cleanDir, ".git"))) {
759
907
  try {
760
- await simpleGit.simpleGit().raw(["config", "--global", "--add", "safe.directory", dir]);
761
- const tempGit = simpleGit.simpleGit(dir).env({
762
- ...process.env,
763
- GCM_INTERACTIVE: "false",
764
- GIT_TERMINAL_PROMPT: "0",
765
- GIT_ASKPASS: "echo"
766
- });
767
- return await tempGit.checkIsRepo();
908
+ await createSafeGit().raw(["config", "--global", "--add", "safe.directory", cleanDir]);
768
909
  } catch {
769
- return true;
770
910
  }
911
+ return true;
771
912
  }
772
- return false;
913
+ } catch {
773
914
  }
915
+ const root = await findGitRoot(cleanDir);
916
+ return root !== null;
774
917
  }
775
918
  async function initializeRepo(sessionId, dir) {
776
919
  try {
777
- const tempGit = simpleGit.simpleGit({
778
- baseDir: dir,
779
- unsafe: {
780
- allowUnsafeEditor: true,
781
- allowUnsafeAskPass: true,
782
- allowUnsafeCredentialHelper: true
783
- }
920
+ recordSessionPath(sessionId, dir);
921
+ await fs__namespace.mkdir(dir, { recursive: true }).catch(() => {
784
922
  });
785
- await tempGit.init();
786
- const branches = await tempGit.branchLocal();
787
- if (branches.all.length === 0) {
788
- await tempGit.raw([
789
- "-c",
790
- "user.name=gitelectron",
791
- "-c",
792
- "user.email=noreply@gitelectron.local",
793
- "commit",
794
- "--allow-empty",
795
- "-m",
796
- "Initial repository setup"
797
- ]);
923
+ try {
924
+ await createSafeGit().raw(["config", "--global", "--add", "safe.directory", dir]);
925
+ } catch {
798
926
  }
799
- await setupDefaultGitAttributes(dir);
927
+ const tempGit = createSafeGit(dir, sessionId);
928
+ await tempGit.init();
800
929
  initGit(sessionId, dir);
930
+ try {
931
+ const branches = await tempGit.branchLocal();
932
+ if (branches.all.length === 0) {
933
+ await tempGit.raw([
934
+ "-c",
935
+ "user.name=gitelectron",
936
+ "-c",
937
+ "user.email=noreply@gitelectron.local",
938
+ "-c",
939
+ "commit.gpgsign=false",
940
+ "commit",
941
+ "--allow-empty",
942
+ "--no-verify",
943
+ "--no-gpg-sign",
944
+ "-m",
945
+ "Initial repository setup"
946
+ ]);
947
+ }
948
+ } catch (commitErr) {
949
+ console.warn("Initial commit in new repo skipped or failed:", commitErr);
950
+ }
951
+ try {
952
+ await setupDefaultGitAttributes(dir);
953
+ } catch (attrErr) {
954
+ console.warn("Default gitattributes setup failed:", attrErr);
955
+ }
801
956
  return true;
802
957
  } catch (e) {
803
958
  console.error("Failed to init repo", e);
804
- return false;
959
+ throw new Error(`Failed to initialize Git repository in ${dir}: ${e?.message || e}`);
960
+ }
961
+ }
962
+ async function checkAndHealZombieGitState(repoPath) {
963
+ if (!repoPath) return;
964
+ try {
965
+ const gitDir = path__namespace.join(repoPath, ".git");
966
+ if (!fs$1.existsSync(gitDir)) return;
967
+ const rebaseMerge = path__namespace.join(gitDir, "rebase-merge");
968
+ const rebaseApply = path__namespace.join(gitDir, "rebase-apply");
969
+ const rebaseHead = path__namespace.join(gitDir, "REBASE_HEAD");
970
+ const indexLock = path__namespace.join(gitDir, "index.lock");
971
+ if (fs$1.existsSync(rebaseMerge) || fs$1.existsSync(rebaseApply) || fs$1.existsSync(rebaseHead)) {
972
+ console.warn(
973
+ "Zombie or interrupted rebase detected at:",
974
+ repoPath,
975
+ "- attempting git rebase --abort"
976
+ );
977
+ try {
978
+ const git = createSafeGit(repoPath);
979
+ await git.raw(["rebase", "--abort"]);
980
+ } catch (abortErr) {
981
+ console.warn("git rebase --abort notice:", abortErr);
982
+ }
983
+ if (fs$1.existsSync(rebaseMerge)) {
984
+ await fs__namespace.rm(rebaseMerge, { recursive: true, force: true }).catch(() => {
985
+ });
986
+ }
987
+ if (fs$1.existsSync(rebaseApply)) {
988
+ await fs__namespace.rm(rebaseApply, { recursive: true, force: true }).catch(() => {
989
+ });
990
+ }
991
+ if (fs$1.existsSync(rebaseHead)) {
992
+ await fs__namespace.rm(rebaseHead, { force: true }).catch(() => {
993
+ });
994
+ }
995
+ }
996
+ if (fs$1.existsSync(indexLock)) {
997
+ try {
998
+ const stats = await fs__namespace.stat(indexLock);
999
+ if (Date.now() - stats.mtimeMs > 5e3) {
1000
+ console.warn("Stale index.lock detected at:", repoPath, "- removing lock");
1001
+ await fs__namespace.unlink(indexLock).catch(() => {
1002
+ });
1003
+ }
1004
+ } catch {
1005
+ }
1006
+ }
1007
+ } catch (e) {
1008
+ console.warn("checkAndHealZombieGitState error:", e);
805
1009
  }
806
1010
  }
807
1011
  async function getStatus(sessionId) {
808
1012
  const { git, repoPath } = getGitSession(sessionId);
809
- return new Promise((resolve, reject) => {
810
- const child = child_process.spawn("git", ["status", "--porcelain"], { cwd: repoPath });
811
- let count = 0;
812
- let isMassive = false;
813
- child.stdout.on("data", (chunk) => {
814
- for (let i = 0; i < chunk.length; ++i) {
815
- if (chunk[i] === 10) {
816
- count++;
817
- if (count > 1e4) {
818
- isMassive = true;
819
- child.kill();
820
- break;
1013
+ if (repoPath) {
1014
+ await checkAndHealZombieGitState(repoPath);
1015
+ }
1016
+ const status = await git.status();
1017
+ const result = JSON.parse(JSON.stringify(status));
1018
+ result.isMassive = false;
1019
+ if (!result.staged) result.staged = [];
1020
+ if (!result.tracking && result.current) {
1021
+ try {
1022
+ const remotes = await git.getRemotes();
1023
+ if (remotes.length > 0) {
1024
+ const remoteName = remotes[0].name || "origin";
1025
+ const trackingRef = `refs/remotes/${remoteName}/${result.current}`;
1026
+ const refExists = await git.raw(["rev-parse", "--verify", trackingRef]).catch(() => null);
1027
+ if (refExists) {
1028
+ await git.raw(["branch", `--set-upstream-to=${remoteName}/${result.current}`, result.current]).catch(() => {
1029
+ });
1030
+ result.tracking = `${remoteName}/${result.current}`;
1031
+ try {
1032
+ const revCount = await git.raw([
1033
+ "rev-list",
1034
+ "--left-right",
1035
+ "--count",
1036
+ `${result.current}...${remoteName}/${result.current}`
1037
+ ]);
1038
+ const parts = revCount.trim().split(/\s+/);
1039
+ if (parts.length === 2) {
1040
+ result.ahead = parseInt(parts[0], 10) || 0;
1041
+ result.behind = parseInt(parts[1], 10) || 0;
1042
+ }
1043
+ } catch {
821
1044
  }
822
1045
  }
823
1046
  }
824
- });
825
- child.on("close", async () => {
826
- if (isMassive) {
827
- resolve({
828
- not_added: [],
829
- created: [],
830
- deleted: [],
831
- modified: [],
832
- conflicted: [],
833
- renamed: [],
834
- staged: [],
835
- files: [],
836
- current: "main",
837
- ahead: 0,
838
- behind: 0,
839
- tracking: null,
840
- isMassive: true,
841
- totalChanges: count
842
- });
843
- } else {
844
- try {
845
- const status = await git.status();
846
- const result = JSON.parse(JSON.stringify(status));
847
- result.isMassive = false;
848
- result.totalChanges = count;
849
- if (!result.staged) result.staged = [];
850
- resolve(result);
851
- } catch (e) {
852
- reject(e);
853
- }
854
- }
855
- });
856
- child.on("error", reject);
857
- });
1047
+ } catch {
1048
+ }
1049
+ }
1050
+ result.totalChanges = result.files?.length || 0;
1051
+ return result;
858
1052
  }
859
1053
  async function stageFiles(sessionId, files) {
860
1054
  const { git, repoPath } = getGitSession(sessionId);
@@ -969,7 +1163,7 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
969
1163
  await ensureAiWeightsUseLfs(git);
970
1164
  if (onProgress) onProgress("Starting rapid file count to prepare massive chunking...");
971
1165
  const totalFiles = await new Promise((resolve) => {
972
- const child2 = child_process.spawn("git", ["status", "--porcelain"], { cwd: repoPath });
1166
+ const child2 = child_process.spawn(getGitBinary(), ["status", "--porcelain"], { cwd: repoPath });
973
1167
  let count = 0;
974
1168
  child2.stdout.on("data", (chunk) => {
975
1169
  for (let i = 0; i < chunk.length; ++i) {
@@ -994,7 +1188,7 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
994
1188
  if (remoteObj && remoteObj.refs.push) {
995
1189
  authUrl = injectAuthToken(sessionId, remoteObj.refs.push);
996
1190
  }
997
- const child = child_process.spawn("git", ["status", "--porcelain", "-z"], { cwd: repoPath });
1191
+ const child = child_process.spawn(getGitBinary(), ["status", "--porcelain", "-z"], { cwd: repoPath });
998
1192
  let buffer = "";
999
1193
  let currentChunk = [];
1000
1194
  let chunkCount = 0;
@@ -1118,6 +1312,7 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
1118
1312
  "-c",
1119
1313
  "lfs.concurrenttransfers=8",
1120
1314
  "push",
1315
+ "--set-upstream",
1121
1316
  "--progress",
1122
1317
  "--no-verify"
1123
1318
  ];
@@ -1141,15 +1336,7 @@ async function pushMassiveRepository(sessionId, remote, branch, chunkSize = 5e3,
1141
1336
  async function pushToRemote(sessionId, remote, branch) {
1142
1337
  const remotes = await getRemotes(sessionId);
1143
1338
  const remoteObj = remotes.find((r) => r.name === remote);
1144
- const tempGit = simpleGit.simpleGit({
1145
- baseDir: getGitSession(sessionId).repoPath,
1146
- unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
1147
- }).env({
1148
- ...process.env,
1149
- GCM_INTERACTIVE: "false",
1150
- GIT_TERMINAL_PROMPT: "0",
1151
- GIT_ASKPASS: "echo"
1152
- });
1339
+ const tempGit = createSafeGit(getGitSession(sessionId).repoPath, sessionId);
1153
1340
  const doPush = async () => {
1154
1341
  const pushArgs = [
1155
1342
  "-c",
@@ -1165,6 +1352,7 @@ async function pushToRemote(sessionId, remote, branch) {
1165
1352
  "-c",
1166
1353
  "lfs.concurrenttransfers=8",
1167
1354
  "push",
1355
+ "--set-upstream",
1168
1356
  "--progress",
1169
1357
  "--no-verify"
1170
1358
  ];
@@ -1242,17 +1430,7 @@ async function masterForcePush(sessionId, remote, branch) {
1242
1430
  console.warn("Commit note:", e.message);
1243
1431
  }
1244
1432
  }
1245
- const tempGit = simpleGit.simpleGit({
1246
- baseDir: repoPath,
1247
- unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
1248
- }).env({
1249
- ...process.env,
1250
- GCM_INTERACTIVE: "false",
1251
- GIT_TERMINAL_PROMPT: "0",
1252
- GIT_ASKPASS: "echo",
1253
- GIT_LFS_SKIP_SMUDGE: "1",
1254
- GIT_LFS_SKIP_PUSH: "1"
1255
- });
1433
+ const tempGit = createSafeGit(repoPath, sessionId);
1256
1434
  const pushArgs = [
1257
1435
  "-c",
1258
1436
  "credential.helper=",
@@ -1261,6 +1439,7 @@ async function masterForcePush(sessionId, remote, branch) {
1261
1439
  "-c",
1262
1440
  "lfs.allowincompletepush=true",
1263
1441
  "push",
1442
+ "--set-upstream",
1264
1443
  "--progress",
1265
1444
  "--no-verify",
1266
1445
  "-f"
@@ -1340,7 +1519,7 @@ async function pullFromRemote(sessionId, remote, branch) {
1340
1519
  targetBranch
1341
1520
  ];
1342
1521
  const pullOutput = await new Promise((resolve, reject) => {
1343
- const child = child_process.spawn("git", pullArgs, {
1522
+ const child = child_process.spawn(getGitBinary(), pullArgs, {
1344
1523
  cwd: repoPath,
1345
1524
  env: {
1346
1525
  ...process.env,
@@ -1415,7 +1594,7 @@ async function checkRemoteUpdates(sessionId) {
1415
1594
  const authUrl = remoteObj.refs?.fetch ? injectAuthToken(sessionId, remoteObj.refs.fetch) : remoteObj.name;
1416
1595
  await new Promise((resolve) => {
1417
1596
  const child = child_process.spawn(
1418
- "git",
1597
+ getGitBinary(),
1419
1598
  [
1420
1599
  "-c",
1421
1600
  "credential.helper=",
@@ -1473,19 +1652,7 @@ async function getBranches(sessionId) {
1473
1652
  const remotes = await git.getRemotes(true);
1474
1653
  if (remotes.length > 0) {
1475
1654
  const remoteObj = remotes[0];
1476
- const tempGit = simpleGit.simpleGit({
1477
- baseDir: getGitSession(sessionId).repoPath,
1478
- unsafe: {
1479
- allowUnsafeEditor: true,
1480
- allowUnsafeAskPass: true,
1481
- allowUnsafeCredentialHelper: true
1482
- }
1483
- }).env({
1484
- ...process.env,
1485
- GCM_INTERACTIVE: "false",
1486
- GIT_TERMINAL_PROMPT: "0",
1487
- GIT_ASKPASS: "echo"
1488
- });
1655
+ const tempGit = createSafeGit(getGitSession(sessionId).repoPath, sessionId);
1489
1656
  const FETCH_TIMEOUT_MS = 15e3;
1490
1657
  const fetchPromise = remoteObj.refs.fetch ? tempGit.fetch(injectAuthToken(sessionId, remoteObj.refs.fetch), { "--prune": null }) : tempGit.fetch(remoteObj.name, { "--prune": null });
1491
1658
  await Promise.race([
@@ -1569,19 +1736,7 @@ async function createBranch(sessionId, branch, baseBranch) {
1569
1736
  const remoteName = remotes[0].name;
1570
1737
  await git.addConfig(`branch.${branch}.remote`, remoteName);
1571
1738
  await git.addConfig(`branch.${branch}.merge`, `refs/heads/${branch}`);
1572
- const tempGit = simpleGit.simpleGit({
1573
- baseDir: getGitSession(sessionId).repoPath,
1574
- unsafe: {
1575
- allowUnsafeEditor: true,
1576
- allowUnsafeAskPass: true,
1577
- allowUnsafeCredentialHelper: true
1578
- }
1579
- }).env({
1580
- ...process.env,
1581
- GCM_INTERACTIVE: "false",
1582
- GIT_TERMINAL_PROMPT: "0",
1583
- GIT_ASKPASS: "echo"
1584
- });
1739
+ const tempGit = createSafeGit(getGitSession(sessionId).repoPath, sessionId);
1585
1740
  const remoteObj = remotes[0];
1586
1741
  if (remoteObj && remoteObj.refs.push) {
1587
1742
  const authUrl = injectAuthToken(sessionId, remoteObj.refs.push);
@@ -1597,25 +1752,31 @@ async function createBranch(sessionId, branch, baseBranch) {
1597
1752
  }
1598
1753
  async function addRemote(sessionId, name, url) {
1599
1754
  const { git } = getGitSession(sessionId);
1755
+ try {
1756
+ const remotes = await git.getRemotes(false);
1757
+ if (remotes.some((r) => r.name === name)) {
1758
+ return await git.remote(["set-url", name, url]);
1759
+ }
1760
+ } catch {
1761
+ }
1600
1762
  return await git.addRemote(name, url);
1601
1763
  }
1602
1764
  async function pushAllBranches(sessionId, remote) {
1603
1765
  const remotes = await getRemotes(sessionId);
1604
1766
  const remoteObj = remotes.find((r) => r.name === remote);
1605
- const tempGit = simpleGit.simpleGit({
1606
- baseDir: getGitSession(sessionId).repoPath,
1607
- unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
1608
- }).env({
1609
- ...process.env,
1610
- GCM_INTERACTIVE: "false",
1611
- GIT_TERMINAL_PROMPT: "0",
1612
- GIT_ASKPASS: "echo"
1613
- });
1767
+ const tempGit = createSafeGit(getGitSession(sessionId).repoPath, sessionId);
1614
1768
  if (remoteObj && remoteObj.refs.push) {
1615
1769
  const authUrl = injectAuthToken(sessionId, remoteObj.refs.push);
1616
- return await tempGit.raw(["-c", "credential.helper=", "push", authUrl, "--all"]);
1770
+ return await tempGit.raw([
1771
+ "-c",
1772
+ "credential.helper=",
1773
+ "push",
1774
+ "--set-upstream",
1775
+ authUrl,
1776
+ "--all"
1777
+ ]);
1617
1778
  }
1618
- return await tempGit.raw(["-c", "credential.helper=", "push", remote, "--all"]);
1779
+ return await tempGit.raw(["-c", "credential.helper=", "push", "--set-upstream", remote, "--all"]);
1619
1780
  }
1620
1781
  async function getCommitHistory(sessionId) {
1621
1782
  const { git } = getGitSession(sessionId);
@@ -1685,7 +1846,7 @@ async function setGitConfig(sessionId, key, value, global = false) {
1685
1846
  gitInstance = getGitSession(sessionId).git;
1686
1847
  } catch (e) {
1687
1848
  if (global) {
1688
- gitInstance = simpleGit.simpleGit();
1849
+ gitInstance = createSafeGit();
1689
1850
  } else {
1690
1851
  throw e;
1691
1852
  }
@@ -1739,10 +1900,11 @@ async function setupUniversalWorkspace(sessionId, remoteUrl, branch, baseBranch,
1739
1900
  const tempId = crypto.randomUUID();
1740
1901
  const workspacePath = path__namespace.join(electron.app.getPath("temp"), `gitelectron_universal_${tempId}`);
1741
1902
  await fs__namespace.mkdir(workspacePath, { recursive: true });
1742
- const git = simpleGit.simpleGit({
1743
- baseDir: workspacePath,
1744
- unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
1745
- });
1903
+ try {
1904
+ await createSafeGit().raw(["config", "--global", "--add", "safe.directory", workspacePath]);
1905
+ } catch {
1906
+ }
1907
+ const git = createSafeGit(workspacePath, sessionId);
1746
1908
  await git.init();
1747
1909
  await setupDefaultGitAttributes(workspacePath);
1748
1910
  let authUrl = remoteUrl;
@@ -1765,9 +1927,13 @@ async function setupUniversalWorkspace(sessionId, remoteUrl, branch, baseBranch,
1765
1927
  await git.addConfig("user.name", "gitelectron");
1766
1928
  await git.addConfig("user.email", "noreply@gitelectron.local");
1767
1929
  }
1768
- await git.raw(["sparse-checkout", "init", "--cone"]);
1769
- const sparsePath = destinationPath === "/" || destinationPath === "" ? "" : destinationPath;
1770
- await git.raw(["sparse-checkout", "set", sparsePath]);
1930
+ try {
1931
+ await git.raw(["sparse-checkout", "init", "--cone"]);
1932
+ const sparsePath = destinationPath === "/" || destinationPath === "" ? "" : destinationPath;
1933
+ await git.raw(["sparse-checkout", "set", sparsePath]);
1934
+ } catch (sparseErr) {
1935
+ console.warn("Sparse checkout setup notice:", sparseErr);
1936
+ }
1771
1937
  const branchToFetch = baseBranch || branch;
1772
1938
  try {
1773
1939
  await git.fetch(["--depth", "1", "origin", branchToFetch]);
@@ -1795,7 +1961,7 @@ async function checkUniversalCollisions(workspacePath, sourceFiles, destinationP
1795
1961
  await fs__namespace.copyFile(source, path__namespace.join(destDirFull, path__namespace.basename(source)));
1796
1962
  }
1797
1963
  }
1798
- const git = simpleGit.simpleGit(workspacePath);
1964
+ const git = createSafeGit(workspacePath);
1799
1965
  const status = await git.status();
1800
1966
  return {
1801
1967
  modified: status.modified,
@@ -1803,10 +1969,7 @@ async function checkUniversalCollisions(workspacePath, sourceFiles, destinationP
1803
1969
  };
1804
1970
  }
1805
1971
  async function getUniversalDiff(workspacePath, filePath) {
1806
- const git = simpleGit.simpleGit({
1807
- baseDir: workspacePath,
1808
- unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
1809
- });
1972
+ const git = createSafeGit(workspacePath);
1810
1973
  try {
1811
1974
  const diff = await git.raw(["diff", "--", filePath]);
1812
1975
  return diff;
@@ -1815,15 +1978,7 @@ async function getUniversalDiff(workspacePath, filePath) {
1815
1978
  }
1816
1979
  }
1817
1980
  async function commitUniversalWorkspace(workspacePath, message, branch) {
1818
- const git = simpleGit.simpleGit({
1819
- baseDir: workspacePath,
1820
- unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
1821
- }).env({
1822
- ...process.env,
1823
- GCM_INTERACTIVE: "false",
1824
- GIT_TERMINAL_PROMPT: "0",
1825
- GIT_ASKPASS: "echo"
1826
- });
1981
+ const git = createSafeGit(workspacePath);
1827
1982
  try {
1828
1983
  await ensureAiWeightsUseLfs(git);
1829
1984
  await git.raw([
@@ -1844,12 +1999,12 @@ async function commitUniversalWorkspace(workspacePath, message, branch) {
1844
1999
  }
1845
2000
  await git.commit(message);
1846
2001
  try {
1847
- await git.raw(["-c", "credential.helper=", "push", "origin", branch]);
2002
+ await git.raw(["-c", "credential.helper=", "push", "--set-upstream", "origin", branch]);
1848
2003
  } catch (err) {
1849
2004
  if (err.message && (err.message.includes("non-fast-forward") || err.message.includes("fetch first") || err.message.includes("branch is behind"))) {
1850
2005
  console.log("Universal push rejected. Auto-pulling with rebase...");
1851
2006
  await git.pull("origin", branch, { "--rebase": "false", "--autostash": null, "-X": "ours" });
1852
- await git.raw(["-c", "credential.helper=", "push", "origin", branch]);
2007
+ await git.raw(["-c", "credential.helper=", "push", "--set-upstream", "origin", branch]);
1853
2008
  return;
1854
2009
  }
1855
2010
  throw err;
@@ -1910,7 +2065,7 @@ async function cloneRepository(sessionId, remoteUrl, destPath) {
1910
2065
  await existingGit.raw(["-c", "credential.helper=", "fetch", "--progress", "origin"]);
1911
2066
  await new Promise((resolve) => {
1912
2067
  const child = child_process.spawn(
1913
- "git",
2068
+ getGitBinary(),
1914
2069
  [
1915
2070
  "-c",
1916
2071
  "core.longpaths=true",
@@ -1968,7 +2123,7 @@ async function cloneRepository(sessionId, remoteUrl, destPath) {
1968
2123
  cwd = path__namespace.dirname(destPath);
1969
2124
  }
1970
2125
  await new Promise((resolve, reject) => {
1971
- const child = child_process.spawn("git", cloneArgs, {
2126
+ const child = child_process.spawn(getGitBinary(), cloneArgs, {
1972
2127
  cwd,
1973
2128
  env: {
1974
2129
  ...process.env,
@@ -2061,13 +2216,14 @@ async function removeGitMetadata(_sessionId, repoPath) {
2061
2216
  }
2062
2217
  async function getDirectoryGitInfo(dir) {
2063
2218
  try {
2064
- const isRepo = await isGitRepo(dir);
2219
+ const rootPath = await findGitRoot(dir) || dir;
2220
+ const isRepo = await isGitRepo(rootPath);
2065
2221
  if (!isRepo) return { isRepo: false, remotes: [] };
2066
- const tempGit = createSafeGit(dir);
2067
- let rootPath = void 0;
2222
+ const tempGit = createSafeGit(rootPath);
2223
+ let detectedRoot = void 0;
2068
2224
  try {
2069
- rootPath = (await tempGit.revparse(["--show-toplevel"])).trim();
2070
- if (rootPath) rootPath = path__namespace.normalize(rootPath);
2225
+ detectedRoot = (await tempGit.revparse(["--show-toplevel"])).trim();
2226
+ if (detectedRoot) detectedRoot = path__namespace.normalize(detectedRoot);
2071
2227
  } catch {
2072
2228
  }
2073
2229
  const remotes = await tempGit.getRemotes(true);
@@ -2083,7 +2239,7 @@ async function getDirectoryGitInfo(dir) {
2083
2239
  }
2084
2240
  return {
2085
2241
  isRepo: true,
2086
- rootPath: rootPath || dir,
2242
+ rootPath: detectedRoot || rootPath,
2087
2243
  remotes: JSON.parse(JSON.stringify(remotes)),
2088
2244
  repoName,
2089
2245
  remoteUrl
@@ -2169,7 +2325,7 @@ const GITHUB_MODELS_CONFIG = {
2169
2325
  const CIPHER_ALGO = "aes-256-cbc";
2170
2326
  const CIPHER_SECRET = "GitElectron_AI_Core_Salt_2026_SecureKey";
2171
2327
  const CIPHER_SALT = "salt_gitelectron";
2172
- const ENCRYPTED_BACKUP_PAYLOAD = "1aa43686251fc27545934564c92ebcd9:acac927d94c7479c0df45b9245f61f47bd4184fca2323e6ce971bc5c74dffaac925caa81788ce81374a05c7072301ee2eac10531be99a850453ff3fd23ea21cd11a8fa95d07d19fe1a8abcdd9a282b06d3e391a621137027c03765f65e977ed432c00e0e9627b2dad77ca708273eff14eac1bbfd01085d4b645a271167bae46edec2a09491b877fba73c3d29dc2e0e2120785347c9189db6bc9892a6018b6dea7ac064b991e9687e2df56e983743985f";
2328
+ const ENCRYPTED_BACKUP_PAYLOAD = "19957ea42600d64c44b82d056696e39a:ff16f32b11fb7ea9962dcf348513f3228bba4e48e217bcd75235cea22d63a1bc874fc3e3ef3baa0c7edaaf0bcedafa54e582131e1b09478edfb80218707f92c0";
2173
2329
  function getBackupApiKeys() {
2174
2330
  try {
2175
2331
  const [ivHex, dataHex] = ENCRYPTED_BACKUP_PAYLOAD.split(":");
@@ -2207,6 +2363,26 @@ Return the response strictly as a JSON object with this shape, and absolutely no
2207
2363
  "explanation": "Internal status message...",
2208
2364
  "commandsToRun": ["git merge --abort", "git pull --no-edit -X ours"]
2209
2365
  }`;
2366
+ async function generateWithGemini(genAI, contents) {
2367
+ const models = [
2368
+ "gemini-flash-lite-latest",
2369
+ "gemini-3.5-flash-lite",
2370
+ "gemini-flash-latest",
2371
+ "gemini-3.8-flash"
2372
+ ];
2373
+ let lastErr = null;
2374
+ for (const modelName of models) {
2375
+ try {
2376
+ const model = genAI.getGenerativeModel({ model: modelName });
2377
+ const result = await model.generateContent(contents);
2378
+ return result.response.text();
2379
+ } catch (err) {
2380
+ lastErr = err;
2381
+ continue;
2382
+ }
2383
+ }
2384
+ throw lastErr || new Error("All Gemini model fallbacks failed");
2385
+ }
2210
2386
  async function analyzeGitError(provider, apiKeyInput, errorMsg, gitStatus, repoPath, githubToken) {
2211
2387
  const keys = apiKeyInput ? apiKeyInput.split(",").map((k) => k.trim()).filter((k) => k) : getBackupApiKeys();
2212
2388
  const prompt = `
@@ -2251,9 +2427,7 @@ ${errorMsg}
2251
2427
  try {
2252
2428
  if (provider === "gemini") {
2253
2429
  const genAI = new generativeAi.GoogleGenerativeAI(apiKey);
2254
- const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
2255
- const result = await model.generateContent([{ text: SYSTEM_PROMPT }, { text: prompt }]);
2256
- responseText = result.response.text();
2430
+ responseText = await generateWithGemini(genAI, [{ text: SYSTEM_PROMPT }, { text: prompt }]);
2257
2431
  } else if (provider === "deepseek") {
2258
2432
  const openai = new OpenAI({
2259
2433
  apiKey,
@@ -2406,12 +2580,10 @@ ${content}
2406
2580
  try {
2407
2581
  if (provider === "gemini") {
2408
2582
  const genAI = new generativeAi.GoogleGenerativeAI(apiKey);
2409
- const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
2410
- const result = await model.generateContent([
2583
+ responseText = await generateWithGemini(genAI, [
2411
2584
  { text: AUTO_SYNC_SYSTEM_PROMPT },
2412
2585
  { text: prompt }
2413
2586
  ]);
2414
- responseText = result.response.text();
2415
2587
  }
2416
2588
  responseText = responseText.replace(/```json/g, "").replace(/```/g, "").trim();
2417
2589
  const parsed = JSON.parse(responseText);
@@ -2464,9 +2636,8 @@ ${truncatedDiff}`;
2464
2636
  try {
2465
2637
  if (provider === "gemini") {
2466
2638
  const genAI = new generativeAi.GoogleGenerativeAI(apiKey);
2467
- const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
2468
- const result = await model.generateContent([{ text: systemPrompt }, { text: prompt }]);
2469
- return result.response.text().trim();
2639
+ const text = await generateWithGemini(genAI, [{ text: systemPrompt }, { text: prompt }]);
2640
+ return text.trim();
2470
2641
  }
2471
2642
  } catch (e) {
2472
2643
  console.warn(`[AI Service] Provider ${provider} commit gen failed: ${e.message}`);
@@ -2511,6 +2682,22 @@ let globalOnResolveComplete = null;
2511
2682
  let globalOnMassiveProgress = null;
2512
2683
  const DEBOUNCE_MS = 3e3;
2513
2684
  const changedFiles = /* @__PURE__ */ new Set();
2685
+ function stopAutoSync() {
2686
+ isAutoSyncEnabled = false;
2687
+ if (currentWatcher) {
2688
+ currentWatcher.close();
2689
+ currentWatcher = null;
2690
+ }
2691
+ if (syncTimeout) {
2692
+ clearTimeout(syncTimeout);
2693
+ syncTimeout = null;
2694
+ }
2695
+ if (remotePollInterval) {
2696
+ clearInterval(remotePollInterval);
2697
+ remotePollInterval = null;
2698
+ }
2699
+ changedFiles.clear();
2700
+ }
2514
2701
  function toggleAutoSync(sessionId, repoPath, enabled, onLog, onErrorApproval, onResolveComplete, onMassiveProgress) {
2515
2702
  isAutoSyncEnabled = enabled;
2516
2703
  globalOnLog = onLog;
@@ -2554,9 +2741,9 @@ function toggleAutoSync(sessionId, repoPath, enabled, onLog, onErrorApproval, on
2554
2741
  }, 25e3);
2555
2742
  currentWatcher = chokidar.watch(repoPath, {
2556
2743
  ignored: [
2557
- /(^|[\/\\])\.git([\/\\]|$)/,
2744
+ /(^|[/\\])\.git([/\\]|$)/,
2558
2745
  // strictly ignore .git directory
2559
- /(^|[\/\\])\.DS_Store$/,
2746
+ /(^|[/\\])\.DS_Store$/,
2560
2747
  // strictly ignore .DS_Store files
2561
2748
  /node_modules/,
2562
2749
  /dist/,
@@ -2664,10 +2851,10 @@ async function handleAutoSyncError(sessionId, errorMsg, onLog) {
2664
2851
  }
2665
2852
  if (errorMsg.includes("index.lock")) {
2666
2853
  onLog("Git lock detected. Automatically clearing zombie lock file...");
2667
- const lockPath = require("path").join(repoPath, ".git", "index.lock");
2854
+ const lockPath = path__namespace.join(repoPath, ".git", "index.lock");
2668
2855
  try {
2669
- await require("fs/promises").unlink(lockPath);
2670
- } catch (e) {
2856
+ await fs__namespace.unlink(lockPath);
2857
+ } catch {
2671
2858
  }
2672
2859
  isResolving = false;
2673
2860
  return;
@@ -2676,7 +2863,11 @@ async function handleAutoSyncError(sessionId, errorMsg, onLog) {
2676
2863
  onLog("Pulling remote changes with rebase...");
2677
2864
  const status2 = await getStatus(sessionId);
2678
2865
  const currentBranch = status2.current || "main";
2679
- await git.pull("origin", currentBranch, { "--rebase": "false", "--autostash": null, "-X": "ours" });
2866
+ await git.pull("origin", currentBranch, {
2867
+ "--rebase": "false",
2868
+ "--autostash": null,
2869
+ "-X": "ours"
2870
+ });
2680
2871
  onLog("Pull successful, attempting push again...");
2681
2872
  await pushToRemote(sessionId, "origin", currentBranch);
2682
2873
  onLog("✅ Error resolved and push resumed!");
@@ -2705,7 +2896,7 @@ async function handleAutoSyncError(sessionId, errorMsg, onLog) {
2705
2896
  try {
2706
2897
  const content = await fs__namespace.readFile(path__namespace.join(repoPath, f), "utf-8");
2707
2898
  conflictedFilesContext[f] = content;
2708
- } catch (e) {
2899
+ } catch {
2709
2900
  }
2710
2901
  }
2711
2902
  try {
@@ -2769,7 +2960,7 @@ async function applyAiFixAndResume(sessionId, resolvedCode) {
2769
2960
  }
2770
2961
  try {
2771
2962
  await git.rebase({ "--continue": null });
2772
- } catch (e) {
2963
+ } catch {
2773
2964
  await git.commit("Auto-resolved merge conflict by AI");
2774
2965
  }
2775
2966
  const status = await getStatus(sessionId);
@@ -2800,11 +2991,37 @@ if (process.platform === "darwin") {
2800
2991
  "/usr/sbin",
2801
2992
  "/sbin"
2802
2993
  ];
2803
- const currentPath = process.env.PATH || "";
2804
- const missingPaths = macPaths.filter((p) => !currentPath.includes(p));
2805
- if (missingPaths.length > 0) {
2806
- process.env.PATH = `${missingPaths.join(":")}:${currentPath}`;
2994
+ const pathParts = (process.env.PATH || "").split(":").filter(Boolean);
2995
+ for (const p of macPaths) {
2996
+ if (!pathParts.includes(p)) {
2997
+ pathParts.unshift(p);
2998
+ }
2999
+ }
3000
+ process.env.PATH = pathParts.join(":");
3001
+ } else if (process.platform === "win32") {
3002
+ const localAppData = process.env.LOCALAPPDATA || "";
3003
+ const programFiles = process.env.ProgramFiles || "C:\\Program Files";
3004
+ const programFilesX86 = process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)";
3005
+ const userProfile = process.env.USERPROFILE || "";
3006
+ const programData = process.env.ProgramData || "C:\\ProgramData";
3007
+ const winPaths = [
3008
+ path__namespace.join(programFiles, "Git", "cmd"),
3009
+ path__namespace.join(programFiles, "Git", "bin"),
3010
+ path__namespace.join(programFiles, "Git", "mingw64", "bin"),
3011
+ path__namespace.join(programFilesX86, "Git", "cmd"),
3012
+ path__namespace.join(programFilesX86, "Git", "bin"),
3013
+ path__namespace.join(localAppData, "Programs", "Git", "cmd"),
3014
+ path__namespace.join(localAppData, "Programs", "Git", "bin"),
3015
+ path__namespace.join(userProfile, "scoop", "shims"),
3016
+ path__namespace.join(programData, "chocolatey", "bin")
3017
+ ];
3018
+ const pathParts = (process.env.PATH || "").split(";").filter(Boolean);
3019
+ for (const p of winPaths) {
3020
+ if (!pathParts.includes(p)) {
3021
+ pathParts.unshift(p);
3022
+ }
2807
3023
  }
3024
+ process.env.PATH = pathParts.join(";");
2808
3025
  }
2809
3026
  if (process.platform === "win32") {
2810
3027
  electron.app.commandLine.appendSwitch("disable-features", "CalculateNativeWinOcclusion");
@@ -2833,6 +3050,7 @@ function createWindow(initialPath) {
2833
3050
  mainWindow.on("closed", () => {
2834
3051
  cleanupSession(sessionId);
2835
3052
  cleanupGitSession(sessionId);
3053
+ stopAutoSync();
2836
3054
  });
2837
3055
  mainWindow.webContents.setWindowOpenHandler((details) => {
2838
3056
  electron.shell.openExternal(details.url);
@@ -2878,15 +3096,67 @@ electron.app.on("second-instance", (_event, commandLine, workingDirectory) => {
2878
3096
  });
2879
3097
  if (process.defaultApp) {
2880
3098
  if (process.argv.length >= 2) {
2881
- electron.app.setAsDefaultProtocolClient("gitelectron", process.execPath, [
2882
- require("path").resolve(process.argv[1])
2883
- ]);
3099
+ electron.app.setAsDefaultProtocolClient("gitelectron", process.execPath, [path__namespace.resolve(process.argv[1])]);
2884
3100
  }
2885
3101
  } else {
2886
3102
  electron.app.setAsDefaultProtocolClient("gitelectron");
2887
3103
  }
2888
3104
  electron.app.whenReady().then(async () => {
2889
3105
  utils.electronApp.setAppUserModelId("com.electron");
3106
+ try {
3107
+ createSafeGit().raw(["config", "--global", "safe.directory", "*"]).catch(() => {
3108
+ });
3109
+ } catch {
3110
+ }
3111
+ if (process.platform === "darwin") {
3112
+ const template = [
3113
+ {
3114
+ label: electron.app.name,
3115
+ submenu: [
3116
+ { role: "about" },
3117
+ { type: "separator" },
3118
+ { role: "services" },
3119
+ { type: "separator" },
3120
+ { role: "hide" },
3121
+ { role: "hideOthers" },
3122
+ { role: "unhide" },
3123
+ { type: "separator" },
3124
+ { role: "quit" }
3125
+ ]
3126
+ },
3127
+ {
3128
+ label: "Edit",
3129
+ submenu: [
3130
+ { role: "undo" },
3131
+ { role: "redo" },
3132
+ { type: "separator" },
3133
+ { role: "cut" },
3134
+ { role: "copy" },
3135
+ { role: "paste" },
3136
+ { role: "selectAll" }
3137
+ ]
3138
+ },
3139
+ {
3140
+ label: "View",
3141
+ submenu: [
3142
+ { role: "reload" },
3143
+ { role: "forceReload" },
3144
+ { role: "toggleDevTools" },
3145
+ { type: "separator" },
3146
+ { role: "resetZoom" },
3147
+ { role: "zoomIn" },
3148
+ { role: "zoomOut" },
3149
+ { type: "separator" },
3150
+ { role: "togglefullscreen" }
3151
+ ]
3152
+ },
3153
+ {
3154
+ label: "Window",
3155
+ submenu: [{ role: "minimize" }, { role: "zoom" }, { type: "separator" }, { role: "front" }]
3156
+ }
3157
+ ];
3158
+ electron.Menu.setApplicationMenu(electron.Menu.buildFromTemplate(template));
3159
+ }
2890
3160
  await loadAccounts();
2891
3161
  electron.app.on("browser-window-created", (_, window) => {
2892
3162
  utils.optimizer.watchWindowShortcuts(window);
@@ -2919,25 +3189,33 @@ electron.app.whenReady().then(async () => {
2919
3189
  if (result.canceled || result.filePaths.length === 0) {
2920
3190
  return null;
2921
3191
  }
2922
- const path2 = result.filePaths[0];
2923
- const isRepo = await isGitRepo(path2);
3192
+ const chosenPath = result.filePaths[0];
3193
+ const rootPath = await findGitRoot(chosenPath) || chosenPath;
3194
+ const isRepo = await isGitRepo(rootPath);
3195
+ recordSessionPath(sessionId, rootPath);
2924
3196
  if (isRepo) {
2925
- initGit(sessionId, path2);
3197
+ initGit(sessionId, rootPath);
2926
3198
  }
2927
- await saveLastWorkspace(path2);
2928
- return { path: path2, isRepo };
3199
+ await saveLastWorkspace(rootPath);
3200
+ return { path: rootPath, isRepo };
2929
3201
  });
2930
- electron.ipcMain.handle("set-repo-path", async (event, path2) => {
3202
+ electron.ipcMain.handle("set-repo-path", async (event, inputPath) => {
2931
3203
  const sessionId = event.sender.id;
2932
- const isRepo = await isGitRepo(path2);
3204
+ if (!inputPath) return { path: "", isRepo: false };
3205
+ const rootPath = await findGitRoot(inputPath) || inputPath;
3206
+ const isRepo = await isGitRepo(rootPath);
3207
+ recordSessionPath(sessionId, rootPath);
2933
3208
  if (isRepo) {
2934
- initGit(sessionId, path2);
3209
+ initGit(sessionId, rootPath);
2935
3210
  }
2936
- await saveLastWorkspace(path2);
2937
- return { path: path2, isRepo };
3211
+ await saveLastWorkspace(rootPath);
3212
+ return { path: rootPath, isRepo };
2938
3213
  });
2939
3214
  electron.ipcMain.handle("init-repo", async (event, path2) => {
2940
- return await initializeRepo(event.sender.id, path2);
3215
+ recordSessionPath(event.sender.id, path2);
3216
+ const result = await initializeRepo(event.sender.id, path2);
3217
+ await saveLastWorkspace(path2);
3218
+ return result;
2941
3219
  });
2942
3220
  electron.ipcMain.handle("get-status", async (event) => {
2943
3221
  return await getStatus(event.sender.id);
@@ -3239,9 +3517,8 @@ electron.app.whenReady().then(async () => {
3239
3517
  });
3240
3518
  electron.ipcMain.handle("check-updates", async () => {
3241
3519
  try {
3242
- const https = require("https");
3243
3520
  return new Promise((resolve) => {
3244
- https.get("https://registry.npmjs.org/gitelectron/latest", (res) => {
3521
+ https__namespace.get("https://registry.npmjs.org/gitelectron/latest", (res) => {
3245
3522
  let data = "";
3246
3523
  res.on("data", (chunk) => {
3247
3524
  data += chunk;
@@ -3250,19 +3527,18 @@ electron.app.whenReady().then(async () => {
3250
3527
  try {
3251
3528
  const json = JSON.parse(data);
3252
3529
  resolve(json.version || null);
3253
- } catch (e) {
3530
+ } catch {
3254
3531
  resolve(null);
3255
3532
  }
3256
3533
  });
3257
3534
  }).on("error", () => resolve(null));
3258
3535
  });
3259
- } catch (e) {
3536
+ } catch {
3260
3537
  return null;
3261
3538
  }
3262
3539
  });
3263
3540
  electron.ipcMain.handle("install-update", async () => {
3264
3541
  return new Promise((resolve) => {
3265
- const { spawn } = require("child_process");
3266
3542
  if (process.platform === "win32") {
3267
3543
  const cmdArgs = [
3268
3544
  "/c",
@@ -3271,14 +3547,14 @@ electron.app.whenReady().then(async () => {
3271
3547
  "/c",
3272
3548
  "echo Waiting for Git Electron to close so it can be safely updated... & timeout /t 3 /nobreak > nul & echo Installing Update... & npm i -g gitelectron@latest gitquickpush-cli@latest & echo Update Complete! Starting application... & gitelectron open & exit"
3273
3549
  ];
3274
- const child = spawn("cmd.exe", cmdArgs, {
3550
+ const child = child_process.spawn("cmd.exe", cmdArgs, {
3275
3551
  detached: true,
3276
3552
  stdio: "ignore"
3277
3553
  });
3278
3554
  child.unref();
3279
3555
  } else {
3280
3556
  const shScript = "sleep 3; npm i -g gitelectron@latest gitquickpush-cli@latest; gitelectron open";
3281
- const child = spawn("sh", ["-c", shScript], {
3557
+ const child = child_process.spawn("sh", ["-c", shScript], {
3282
3558
  detached: true,
3283
3559
  stdio: "ignore"
3284
3560
  });